﻿//*************************************************************************************
// File:     Pool.cs
//*************************************************************************************
// Description: Encapsula un fondo de memoria generico, para no tener que realizar
//              llamadas de constructor y destructor constantemente, descargando al
//              administrador de memoria dinamica de tareas irrelevantes y frecuentes.
//*************************************************************************************
// Classes:      AbstractPool<T> : IDisposable where T : new()
//*************************************************************************************
// Author:      ManOguaR
//*************************************************************************************

using System;
using System.Collections.Generic;
using System.Threading;

namespace Xeralabs.Core.Utils.Internals
{
    /// <summary>
    /// Representa un fondo de memoria generico, para no tener que realizar llamadas de constructor y destructor constantemente,
    /// descargando el administrador de memoria dinamica de tareas simples frecuentes.
    /// </summary>
    /// <typeparam name="T">Un tipo con un constructor publico sin parametros.</typeparam>
    public abstract class AbstractPool<T> : IDisposable
            where T : new()
    {
        /// <summary>
        /// Contiene los objetos actualmente en el fondo de memoria.
        /// </summary>
        protected List<T> Pool = new List<T>();
        private int _nbrObjects = 0;
        private int _maxSize = 5;
        /// <summary>
        /// Devuelve el tamaño maximo de la reserva de memoria.
        /// </summary>
        public int MaxSize
        {
            get { return this._maxSize; }
        }
        /// <summary>
        /// Devuelve el numero de objetos en el fondo de memoria.
        /// </summary>
        public int NumObjects
        {
            get { return this._nbrObjects; }
        }

        /// <summary>
        /// Constructor por defecto.
        /// </summary>
        public AbstractPool()
        {
            this.Pool.Capacity = this._maxSize;
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="maxSize">El tamaño maximo del fondo de memoria.</param>
        public AbstractPool(int maxSize)
        {
            this._maxSize = maxSize;

            this.Pool.Capacity = this._maxSize;
        }

        /// <summary>
        /// Rellena el fondo de memoria con un numero de objetos determinado por el parametro <see cref="nbrNewObjects"/>, hasta alcanzar el tamaño maximo del fondo.
        /// </summary>
        /// <param name="nbrNewObjects">Indica el numero de objetos a añadir al fondo de memoria.</param>
        public void Fill(int nbrNewObjects)
        {
            lock (Pool)
            {
                if (this._nbrObjects + nbrNewObjects > this._maxSize)
                    nbrNewObjects = this._maxSize - this._nbrObjects;

                for (int i = 0; i < nbrNewObjects; i++)
                {
                    this.Pool.Add(CreateNewPoolObject());
                    this._nbrObjects++;
                }
            }
        }

        /// <summary>
        /// Metodo abstracto para la creacion de un objeto del tipo generico <see cref="T"/>.
        /// </summary>
        /// <returns>Un nuevo objeto del tipo generico <see cref="T"/>.</returns>
        protected abstract T CreateNewPoolObject();

        /// <summary>
        /// Devuelve un objeto del fondo de memoria. Si el fondo de memoria esta vacio, se crea un nuevo objeto, hasta el tamaño maximo permitido.
        /// Si se ha alcanzado el tamaño maximo, la solicitud queda en espera hasta que se libera un objeto.
        /// </summary>
        /// <returns>Un objeto del tipo generico <see cref="T"/>, extraido del fondo de memoria.</returns>
        public T GetObject()
        {
            lock (Pool)
            {
                T obj;
                if (Pool.Count == 0 && _nbrObjects < this._maxSize) // no hay objetos pero queda espacio para nuevos.
                {
                    obj = CreateNewPoolObject();
                    _nbrObjects++;
                }
                else
                {
                    // Si no hay objetos, debemos esperar a que se libere al menos uno.
                    while (Pool.Count == 0)
                        Monitor.Wait(Pool);

                    obj = Pool[Pool.Count - 1];   // mas rapido al final.
                    Pool.RemoveAt(Pool.Count - 1);
                }
                return obj;
            }
        }

        /// <summary>
        /// Libera un objeto en uso y lo situa en el fondo de memoria.
        /// </summary>
        /// <param name="obj">El objeto en uso que va a ser liberado.</param>
        public virtual void ReleaseObject(T obj)
        {
            lock (Pool)
            {
                Pool.Add(obj);
                Monitor.Pulse(Pool);
            }
        }

        #region Miembros de IDisposable
        /// <summary>
        /// Metodo abstracto para liberar todos los recursos disponibles.
        /// </summary>
        public abstract void Dispose();
        #endregion
    }
}